﻿using System.CodeDom.Compiler;
using System.IO;
using System.Reflection;
using log4net;

namespace CSharpExecutor
{
    public class Compiler
    {
        #region Constants

        private const string LANGUAGE = "CSharp";
        private const string MATCH_REFERENCE = @"^\#reference\s+\""(?<reference>\w[\:\\\/\d\w\.]*)\""\s*$";

        #endregion

        #region Variables

        private CodeDomProvider _codeProvider;
        private CompilerParameters _parameters;
        private CompilerResults _results;
        private ILog _log;

        #endregion

        #region Constructors

        private Compiler(CompilerParameters parameters, string filename)
        {
            _log = LogManager.GetLogger(typeof(Compiler));
            _codeProvider = CodeDomProvider.CreateProvider(LANGUAGE);
            _parameters = parameters;
            Filename = filename;
            Script = File.ReadAllText(filename);

            PreprocessFile();
            Compile();
        }

        #endregion

        #region Properties

        public string Filename { get; private set; }

        public string Script { get; private set; }

        public Assembly Assembly
        {
            get
            {
                if (_results == null)
                {
                    return null;
                }
                return _results.CompiledAssembly;
            }
        }

        #endregion

        #region Methods

        public static Compiler Compile(CompilerParameters parms, string filename)
        {
            return new Compiler(parms, filename);
        }

        /// <summary>
        /// Process #reference and #include directives in the script.
        /// </summary>
        private void PreprocessFile()
        {
            _log.Info("Launching preprocessor.");

            var preprocessor = new PreProcessor();

            // Add the #reference directive:
            preprocessor.Actions.Add(MATCH_REFERENCE, (lines, index, match) => _parameters.ReferencedAssemblies.Add(match.Groups["reference"].Value));

            Script = preprocessor.ProcessText(Script);
        }

        private void Compile()
        {
            _log.Info("Compiling script.");

            _results = _codeProvider.CompileAssemblyFromSource(_parameters, Script);
            if (_results.Errors.Count > 0)
            {
                foreach (CompilerError error in _results.Errors)
                {
                    _log.Fatal(error);
                }
                _log.Fatal("Unable to compile script.");
            }
        }

        #endregion
    }
}